#include "ExtOSCThread.h"
#include <boost/foreach.hpp>
#include "cinder/app/AppBasic.h"
#include "cinder/Utilities.h"

ExtOSCThread::ExtOSCThread(int port)
    : section(1)
{
    this->receiveSocket = new UdpReceiveSocket(
            IpEndpointName( IpEndpointName::ANY_ADDRESS, port)
    );
    textureFont = gl::TextureFont::create( Font( cinder::app::loadResource("/resources/OCRAEXT.ttf"), 18 ) );
    //textureFont = gl::TextureFont::create( Font( "Trebuchet MS", 18 ) );
    this->start();
}

void ExtOSCThread::run() {
    while(running) {

        listener = new CoYPacketListener(this);
        mux.AttachSocketListener(this->receiveSocket,listener);
        mux.RunUntilSigInt();
    }
}

void ExtOSCThread::start()
{
    running = true;
    thread = boost::shared_ptr<boost::thread>(new boost::thread(boost::bind(&ExtOSCThread::run, this)));
}

void ExtOSCThread::stop()
{
    running = false;
    thread->join();
}


void ExtOSCThread::send(string address, int port, osc::OutboundPacketStream p) {
    this->receiveSocket->SendTo(
            IpEndpointName(
                address.c_str(),
                port
                ),
            p.Data(),p.Size()
        );
}

void ExtOSCThread::drawObjects()
{
    boost::shared_lock<boost::shared_mutex> lock(synthObjectMutex);
    std::pair<std::string,SynthObject*> so;
    BOOST_FOREACH(so, synthObjects) {
        so.second->draw();
    }
}

void ExtOSCThread::drawMetaObject()
{
    boost::shared_lock<boost::shared_mutex> lock(cursorMutex);

    glEnable(GL_BLEND);
    glBlendFunc(GL_ONE_MINUS_DST_COLOR,GL_ONE_MINUS_SRC_COLOR);

    std::pair<std::string,SynthObject*> so;
    BOOST_FOREACH(so, synthObjects) {
        so.second->drawMetaSynth();
    }

    std::pair<std::string,CoYCursor*> co;
    BOOST_FOREACH(co, cursors) {
        co.second->draw();
    }

    glLineWidth(1.0);
    //Draw Synth Connections
    BOOST_FOREACH(so, synthObjects) {
        std::pair<std::string,SynthObject*> so2;
        BOOST_FOREACH(so2, synthObjects) {
            if(fabs(so2.second->getX() - so.second->getX()) < 250) {
                if(fabs(so2.second->getY() - so.second->getY()) < 250) {
                    //cout << "drawing" <<endl;
                    glDisable(GL_BLEND);
                    glDisable(GL_DEPTH_TEST);
                    gl::color(ColorA(1.0,1.0,1.0,1.0));
                    gl::drawLine(Vec2f(so.second->getX(),so.second->getY()),Vec2f(so2.second->getX(),so2.second->getY()));
                }
            }
        }
    }

    chat.draw();
}

void ExtOSCThread::login()
{
    char buffer[1024];
    osc::OutboundPacketStream p( buffer, 1024 );
    p << osc::BeginMessage( "/changePorts" ) << 32244 << 32254 << osc::EndMessage;
    send("127.0.0.1",32243,p);
    p.Clear();
    p << osc::BeginMessage( "/login" ) << osc::EndMessage;
    send("127.0.0.1",32243,p);
}

UdpReceiveSocket* ExtOSCThread::getReceiveSocket() {
    return this->receiveSocket;
}

void ExtOSCThread::quitConnection() {
    this->mux.AsynchronousBreak();
}

void ExtOSCThread::addSynth(string objectID, string synthName, float x,float y, float param1, float param2) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) == synthObjects.end()) {
        synthObjects[objectID] = new SynthObject(synthName,x,y,param1,param2,section,textureFont);
    }
}

void ExtOSCThread::removeSynth(string objectID){
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        delete synthObjects.at(objectID);
        synthObjects.erase(objectID);
    }
}

void ExtOSCThread::updateSynthPositionX(string objectID, int xPos) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setX(xPos);
    }
}

void ExtOSCThread::updateSynthPositionY(string objectID, int yPos) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setY(yPos);
    }
}

void ExtOSCThread::updateSynthParam1(string objectID, float param1) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setParam1(param1);
    }
}

void ExtOSCThread::updateSynthParam2(string objectID, float param2) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setParam2(param2);
    }
}

void ExtOSCThread::updateSynthSelected(string objectID, string userName) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setSynthSelection(userName);
    }
}

void ExtOSCThread::synthNameSync(string objectID, string synthName) {
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(synthObjects.find(objectID) != synthObjects.end()) {
        synthObjects.at(objectID)->setSynthName(synthName);
    }
}

void ExtOSCThread::setSection(int section)
{
    this->section = section;
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    std::pair<std::string,SynthObject*> so;
    BOOST_FOREACH(so,synthObjects) {
        so.second->setSection(section);
    }
}

void ExtOSCThread::addCursor(string objectID, float x,float y) {
    boost::upgrade_lock<boost::shared_mutex> lock(cursorMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(cursors.find(objectID) == cursors.end()) {
        cursors[objectID] = new CoYCursor(objectID,x,y,textureFont);
    }
}

void ExtOSCThread::removeCursor(string objectID){
    boost::upgrade_lock<boost::shared_mutex> lock(cursorMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(cursors.find(objectID) != cursors.end()) {
        delete cursors.at(objectID);
        cursors.erase(objectID);
    }
}

void ExtOSCThread::setCursorX(string objectID, float xPos) {
    boost::upgrade_lock<boost::shared_mutex> lock(cursorMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(cursors.find(objectID) != cursors.end()) {
        cursors.at(objectID)->setX(xPos);
    }
}

void ExtOSCThread::setCursorY(string objectID, float yPos) {
    boost::upgrade_lock<boost::shared_mutex> lock(cursorMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    if(cursors.find(objectID) != cursors.end()) {
        cursors.at(objectID)->setY(yPos);
    }
}

void ExtOSCThread::receiveChat(string chat)
{
    this->chat.receiveChat(chat);
}

void ExtOSCThread::setAudioBuffer(vector<float> buffer)
{
    boost::upgrade_lock<boost::shared_mutex> lock(synthObjectMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock(lock);
    boost::upgrade_lock<boost::shared_mutex> lock2(bufferMutex);
    boost::upgrade_to_unique_lock<boost::shared_mutex> uniqueLock2(lock2);
    std::pair<std::string,SynthObject*> so;
    this->buffer = buffer;
    BOOST_FOREACH(so,synthObjects) {
        so.second->setAudioBuffer(buffer);
    }
}

int ExtOSCThread::getObjectCount() {
    boost::shared_lock<boost::shared_mutex> lock(synthObjectMutex);

    return synthObjects.size();
}
